﻿using System;
using System.Collections.Generic;
using System.Text;
using Reflector;
using System.Reflection;
using dAmn;
using System.Diagnostics;
using llAma.Backend.Interface;
using llAma;

namespace llamaServer
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class PacketHandlerAttribute : Attribute
    {
        protected string mCommand = "";
        protected string mSubCommand = "*";
        public string Command
        {
            get { return mCommand; }
        }
        public string SubCommand
        { get { return mSubCommand; } }
        /// <summary>
        /// Creates a new instance of the PacketHandler attribute.
        /// Specifing which packet is actually handled.
        /// </summary>
        /// <param name="commandmethod">Packet to handle, use * to trap all packets.</param>
        public PacketHandlerAttribute(string commandmethod)
        {
            mCommand = commandmethod;
        }
        public PacketHandlerAttribute(string commandmethod, string subcommand)
            : this(commandmethod)
        {
            mSubCommand = subcommand;
        }
    }
    public class PacketProcessor
    {
        public PacketProcessor(object o)
        {
            this.AddHandlers(o);
        }
        private static bool PredicateMatcher(MethodInfo mi)
        {
            if (mi.GetParameters().Length != 2) return false;
            if(mi.GetParameters()[0].ParameterType != typeof(Packet)) return false;
            if (mi.GetParameters()[1].ParameterType != typeof(IConnection) && mi.GetParameters()[1].ParameterType != typeof(object)) return false;
            if (mi.GetParameters()[1].ParameterType == typeof(object))
            {
                Debug.WriteLine("WARNING: Method " + mi.ToString() + " implemented in " + mi.DeclaringType.ToString() + " uses an object instead of IConnection");
            }
            return true;
        }
        private Dictionary<string, List<MethodAndAttribute<PacketHandlerAttribute>>> methodDict =
            new Dictionary<string, List<MethodAndAttribute<PacketHandlerAttribute>>>();
        public void AddHandlers(object o)
        {
            lock (methodDict)
            {
                MethodAndAttribute<PacketHandlerAttribute>[] methods = Reflector<PacketHandlerAttribute>.Search(o, PredicateMatcher);
                foreach (MethodAndAttribute<PacketHandlerAttribute> m in methods)
                {
                    List<MethodAndAttribute<PacketHandlerAttribute>> handlers = GetMethods(m.Attribute.Command);
                    handlers.Add(m);
                    SetMethods(m.Attribute.Command, handlers);
                }
            }
        }
        public void ExecuteMethodsOnCommand(Packet p, object sender)
        {
            if (!this.Predicate(p)) return;
            //Method dict cannot be modified, This lock is not needed.
            //lock (methodDict)
            {
                //Fetch all handlers for this packet.
                List<MethodAndAttribute<PacketHandlerAttribute>> handlers = GetMethods(p.cmd);
                handlers.AddRange(GetMethods("*"));
                Packet subPacket = p.SubPacket;
                for (int x = 0; x < handlers.Count; x++)
                {
                    //I wish there was a cleaner alternative, But adding a 2nd dict to do lookups seems silly.
                    //Performance impact should be minimal.
                    try
                    {
                        if (handlers[x].Attribute.SubCommand != "*")
                        {
                            if(subPacket == null || subPacket.cmd != handlers[x].Attribute.SubCommand)
                            {
                                continue;
                            }
                        }
                        handlers[x].Invoke(p, sender);
                    } 
                    catch
                    {

                    }
                }
            }
        }
        private void SetMethods(string command, List<MethodAndAttribute<PacketHandlerAttribute>> handlers)
        {
                methodDict[command.Trim()] = handlers;
        }
        private Predicate<Packet> mPacketPredicate = null;
        private bool defaultPredicate(Packet p)
        {
            return true;
        }
        public Predicate<Packet> Predicate
        {
            get
            {
                if (mPacketPredicate == null)
                    return defaultPredicate;
                else
                    return mPacketPredicate;
            }
            set { mPacketPredicate = value; }
        }
        public List<MethodAndAttribute<PacketHandlerAttribute>> GetMethods(string command)
        {
                if (methodDict.ContainsKey(command))
                {
                    return methodDict[command];
                }
                else
                    return new List<MethodAndAttribute<PacketHandlerAttribute>>();
        }
    }
}
